package engine.display;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * Manages input. Input is buffered; successive key presses will be used on a LIFO basis.
 */
public class Input implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {
	/**
	 * The input buffer. This is hopefully synchronized well enough that I don't have to deal with
	 * it.
	 */
	private BlockingDeque<InputEvent> buffer = new LinkedBlockingDeque<InputEvent>();

	private final Display display;
	private int mouseX;

	private int mouseY;

	private boolean showMouse;

	/**
	 * Constructs an Input object. This is closely associated with a display.
	 * 
	 * @param display
	 *            the display to associate this input with.
	 */
	public Input(Display display) {
		this.display = display;
		display.frame.addKeyListener(this);
		display.frame.addMouseListener(this);
		display.frame.addMouseMotionListener(this);
		display.frame.addMouseWheelListener(this);
	}

	/**
	 * @param display
	 *            the display object. This method has to refresh the screen while waiting for input.
	 * @return the next InputEvent from the input buffer. If there is no available InputEvent,
	 *         blocks until the user presses a key, or clicks the mouse.
	 */
	public InputEvent getInputEvent(Display display) {
		InputEvent k = null;
		while (k == null) {
			// poll for input
			k = buffer.pollFirst();
			if (k == null) {
				// redraw the screen if we have to wait for input
				display.render();
			}
		}
		return k;
	}

	/**
	 * @return the x position of the mouse on the screen (in cells)
	 */
	public int getMouseX() {
		return mouseX;
	}

	/**
	 * @return the y position of the mouse on the screen (in cells)
	 */
	public int getMouseY() {
		return mouseY;
	}

	@Override
	public void keyPressed(KeyEvent e) {
		try {
			// put the key event in a synchronized buffer
			buffer.putLast(e);
		} catch (InterruptedException e1) {
			// good, we can get more input now
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {}

	@Override
	public void keyTyped(KeyEvent e) {}

	@Override
	public void mouseClicked(MouseEvent e) {
		try {
			// map the mouse position to a tile on the board
			int deltaX = ((e.getX() - display.xoff) / display.getCellWidth()) - e.getX();
			int deltaY = ((e.getY() - display.yoff) / display.getCellHeight()) - e.getY();
			e.translatePoint(deltaX, deltaY);

			// put the mouse event in a synchronized buffer
			buffer.putLast(e);
		} catch (InterruptedException e1) {
			// good, we can get more input now
		}
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		mouseX = (e.getX() - display.xoff) / display.getCellWidth();
		mouseY = (e.getY() - display.yoff) / display.getCellHeight();

	}

	@Override
	public void mouseEntered(MouseEvent e) {
		showMouse = true;

	}

	@Override
	public void mouseExited(MouseEvent e) {
		showMouse = false;

	}

	@Override
	public void mouseMoved(MouseEvent e) {
		mouseX = (e.getX() - display.xoff) / display.getCellWidth();
		mouseY = (e.getY() - display.yoff) / display.getCellHeight();
	}

	@Override
	public void mousePressed(MouseEvent e) {}

	@Override
	public void mouseReleased(MouseEvent e) {}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		try {
			// map the mouse position to a tile on the board
			int deltaX = ((e.getX() - display.xoff) / display.getCellWidth()) - e.getX();
			int deltaY = ((e.getY() - display.yoff) / display.getCellHeight()) - e.getY();
			e.translatePoint(deltaX, deltaY);

			// put the mouse event in a synchronized buffer
			buffer.putLast(e);
		} catch (InterruptedException e1) {
			// good, we can get more input now
		}
	}

	/**
	 * Draws the input (debugging only)
	 * 
	 * @param graphics2d
	 *            the raw graphics object for the display
	 */
	public void render(Graphics2D graphics2d) {
		if (showMouse) {
			graphics2d.setColor(Color.white);
			graphics2d.drawRect(mouseX * display.getCellWidth(), mouseY * display.getCellHeight(),
					display.getCellWidth(), display.getCellHeight());
		}
	}
}
